#define _GNU_SOURCE 1
#include <string.h>

#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <limits.h>
#include <beecrypt/md5.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <archive.h>
#include <archive_entry.h>
#include <assert.h>

#include <uriparser/Uri.h>

#include "rpm_tags.h"
#include "rpm_priv.h"
#include "rpm.h"

#define INITIAL_RPM_READ_SIZE 256*1024
#define READ_BUFFER_SIZE      64*1024

typedef struct NetRequest
{
    CURL * handle;
    CURLM *multi_handle;
    void * buffer;
    size_t buffer_len;
    off_t buffer_pos;
    int in_progress;
    int allowed_to_grow;

} NetRequest;

static size_t
netReadCallback( void *ptr, size_t size, size_t nmemb, void *stream )
{
    NetRequest * req = (NetRequest*)stream;

    size_t actual = nmemb*size;
    size_t avail_buffer = req->buffer_len - req->buffer_pos;

    if( avail_buffer < actual )
    {
    	if(req->allowed_to_grow )
    	{
			void * newBuf = realloc( req->buffer, req->buffer_len + ( actual - avail_buffer ) );
			if( newBuf != NULL )
			{
				req->buffer = newBuf;
				req->buffer_len = req->buffer_len + ( actual - avail_buffer );
			}
			else
			{
				/* Failed to realloc buffer.  Only grab what we can. */
				actual = avail_buffer;
			}
    	}
    	else
    	{
    		actual=avail_buffer;

    	}
    }
    memcpy( req->buffer + req->buffer_pos, ptr, actual );
    req->buffer_pos += actual;


    return( actual );
}

static ssize_t
url_pread( CURL * session, void *buf, size_t count, off_t offset )
{
    char range_request[128];

    sprintf( range_request, "%zd-%zd", offset, ( count+offset-1 ) );


    NetRequest readSession = { session, NULL, buf, count, 0, 0,0 };

    curl_easy_setopt( session, CURLOPT_WRITEFUNCTION, netReadCallback );
    curl_easy_setopt( session, CURLOPT_WRITEDATA, &readSession );
    curl_easy_setopt( session, CURLOPT_RANGE, range_request );


    CURLcode rc  = curl_easy_perform( session );

    if( rc != 0 && rc != CURLE_WRITE_ERROR )
    {
        return( -1 );
    }

    return( readSession.buffer_pos );

}

char *
NormalizePath( const char * userString )
{
    char *retVal = NULL;
    /*
     * Normalize the path.
     */
    UriParserStateA state;
    UriUriA uri;

    state.uri = &uri;
    if( uriParseUriA( &state, userString ) != URI_SUCCESS )
    {
        /* Failure */
        uriFreeUriMembersA( &uri );
        return( NULL );
    }

    if( uri.scheme.first == NULL )
    {
        char normalPath[PATH_MAX+1] = {0};
        /* Probably was a raw filesystem path. */
        if( userString[0] != '/' )
        {
            /* Relative path.  Normalize it. */
            getcwd( normalPath, PATH_MAX );
            strcat( normalPath, "/" );
            strcat( normalPath, userString );
        }
        else
        {
            strcpy( normalPath, userString );
        }
        size_t bytesNeeded = 7 + 3 * strlen( normalPath ) + 1;
        retVal = (char*)( malloc( bytesNeeded ) );
        uriUnixFilenameToUriStringA( normalPath, retVal );
    }
    else
    {
        retVal=strdup( userString );
    }
    return( retVal );
}

RpmFileHandle
OpenRpmFile( const char * pathToRpm, RpmStatus * status )
{

    assert( status != NULL );

    *status = rpmStsNoError;

    /* Allocate a new handle and set memory to zeros.*/
    RpmFileHandle handle= ( RpmFile* )( malloc( sizeof( RpmFile ) ) );
    memset( handle, 0, sizeof ( RpmFile ) );
    handle->priv = (RpmPriv*)( malloc( sizeof( RpmPriv ) ) );
    memset( handle->priv, 0, sizeof( RpmPriv ) );

    char * uriString = NormalizePath( pathToRpm );
    if( uriString == NULL )
    {
        *status = rpmNotAUri;
        goto bye;
    }

    /* Initialize the CURL library.  This gets us access to RPM's
     * via URL and local file access.
     */
    char curl_error_buffer[CURL_ERROR_SIZE];
    handle->priv->curl = curl_easy_init();
    curl_easy_setopt( handle->priv->curl, CURLOPT_ERRORBUFFER, curl_error_buffer );
    curl_easy_setopt( handle->priv->curl, CURLOPT_URL, uriString );
    curl_easy_setopt( handle->priv->curl, CURLOPT_VERBOSE, 1 );

    /* We'll load the first few bytes of the RPM to (hopefully) give us the entire header.  If,
     * according to the header information, we don't have enough data, we'll read some more, but
     * INITIAL_RPM_READ_SIZE is selected such that we won't have to.
     *
     * We'll read the payload later.
     */

    void * initialBuffer = malloc( INITIAL_RPM_READ_SIZE );
    ssize_t bytesRead = url_pread( handle->priv->curl, initialBuffer, INITIAL_RPM_READ_SIZE, 0 );

    /* Read the "leader" from the file to see if the thing is an rpm. */
    RpmLead theLeader;

    if( bytesRead == -1 )
    {
        *status = rpmStsReadErr;
        goto bye;
    }

    void * current_pos = initialBuffer;
    if( bytesRead < (ssize_t)( sizeof( RpmLead ) ) )
    {
        *status = rpmStsNotAnRpm;
        goto bye;
    }

    memcpy( &theLeader, initialBuffer, sizeof( RpmLead ) );
    current_pos += sizeof( RpmLead );

    if( memcmp( theLeader.magic, RPM_MAGIC, sizeof( RPM_MAGIC ) )!=0 )
    {
        *status = rpmStsNotAnRpm;
        goto bye;
    }

    /* Load the signature structure header. */
    RpmHeaderRecord theSignatureHeader;

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( sizeof( RpmHeaderRecord ) ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }

    memcpy( &theSignatureHeader, current_pos, sizeof( RpmHeaderRecord ) );
    current_pos += sizeof( RpmHeaderRecord );

    if( memcmp( theSignatureHeader.magic, HEADER_SECTION_MAGIC, sizeof( HEADER_SECTION_MAGIC ) )!=0 )
    {
        *status = rpmStsRpmInconsistencyErr;
        goto bye;
    }

    handle->priv->numSignatureEntries = ntohl( theSignatureHeader.nindex );

    size_t size_of_signature_records = handle->priv->numSignatureEntries * sizeof( RpmHeaderIndexRecord );

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( size_of_signature_records ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }

    handle->priv->signatureEntries =
        (  RpmHeaderIndexRecord * )( malloc( size_of_signature_records ) );

    int index_entry = 0;
    int entry_bytes_read = 0;
    for( index_entry = 0; index_entry < handle->priv->numSignatureEntries; index_entry++ )
    {
        uint32_t tempVal[4];
        memcpy( tempVal, current_pos, sizeof( tempVal ) );
        current_pos += sizeof( tempVal );

        handle->priv->signatureEntries[index_entry].tag = (RpmTag)ntohl( tempVal[0] );
        handle->priv->signatureEntries[index_entry].type = (RpmHeaderElementType)ntohl( tempVal[1] );
        handle->priv->signatureEntries[index_entry].offset = (off_t)ntohl( tempVal[2] );
        handle->priv->signatureEntries[index_entry].count = ntohl( tempVal[3] );
    }

    /* Load the index data from the signature section. */
    size_t signature_size = ntohl( theSignatureHeader.hsize );
    handle->priv->signatureStore = malloc( signature_size );

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( signature_size ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }
    memcpy( handle->priv->signatureStore, current_pos, signature_size );
    current_pos += signature_size;

    /* Load the header section. For whatever reason, the RPM spec has a *section* called
     * "the header" and each section also has a data structure called "Header".  Thus,
     * we're looking at the "HeaderHeader".
     */
    RpmHeaderRecord theHeaderHeader;

    /* The  header record starts on an  8 byte boundary. */
    current_pos += ( 8- ( ( current_pos-initialBuffer ) % 8 ) ) %8;

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( sizeof( RpmHeaderRecord ) ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }

    memcpy( &theHeaderHeader, current_pos, sizeof( RpmHeaderRecord ) );
    current_pos += sizeof( RpmHeaderRecord );
    /* By looking at the header, we can see if we are still in sync */
    if( memcmp( theHeaderHeader.magic, HEADER_SECTION_MAGIC, sizeof( HEADER_SECTION_MAGIC ) )!=0 )
    {
        *status = rpmStsRpmInconsistencyErr;
        goto bye;
    }

    handle->priv->numHeaderEntries = ntohl( theHeaderHeader.nindex );

    size_t header_index_size =  handle->priv->numHeaderEntries * sizeof( RpmHeaderIndexRecord );

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( header_index_size ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }
    handle->priv->headerEntries =(RpmHeaderIndexRecord*)malloc( header_index_size );

    index_entry = 0;
    entry_bytes_read = 0;
    for( index_entry = 0; index_entry < handle->priv->numHeaderEntries; index_entry++ )
    {
        uint32_t * tempVal = (uint32_t*)current_pos;
        current_pos += 4*sizeof( uint32_t );

        handle->priv->headerEntries[index_entry].tag = (RpmTag)ntohl( tempVal[0] );
        handle->priv->headerEntries[index_entry].type = (RpmHeaderElementType)ntohl( tempVal[1] );
        handle->priv->headerEntries[index_entry].offset = (off_t)ntohl( tempVal[2] );
        handle->priv->headerEntries[index_entry].count = ntohl( tempVal[3] );
    }

    size_t header_size = ntohl( theHeaderHeader.hsize );

    if( ( bytesRead-( current_pos-initialBuffer ) ) < (ssize_t)( header_size ) )
    {
        *status = rpmStsReadErr;
        goto bye;
    }

    handle->priv->headerStore = malloc( header_size );
    memcpy( handle->priv->headerStore, current_pos, header_size );
    current_pos += header_size;

    /* Now, we know the payload offset. */
    handle->priv->payloadOffset =current_pos-initialBuffer;

    bye:
    if( *status != rpmStsNoError )
    {
        CloseRpmFile( handle );
        handle=NULL;
    }
    free( uriString );
    free( initialBuffer );
    return( handle );
}

RpmStatus
CloseRpmFile( RpmFileHandle handle )
{
    if( handle )
    {
        curl_easy_cleanup( handle->priv->curl );
        free( handle->priv->signatureEntries );
        free( handle->priv->signatureStore );
        free( handle->priv->headerEntries );
        free( handle->priv->headerStore );
        free( handle->priv );
        free( handle );
    }
    return( rpmStsNoError );
}

void
ExtractScriptContents( RpmFileHandle handle, RpmScript script, int includeShebang, FILE * out )
{
    char * prog = NULL;

    if( includeShebang )
    {
        switch( script )
        {
            case rpmScriptPreinstall:
                prog = rpm_getTagValueAsString( handle, RPMTAG_PREINPROG );
                break;
            case rpmScriptPostinstall:
                prog = rpm_getTagValueAsString( handle, RPMTAG_POSTINPROG );
                break;
            case rpmScriptPreuninstall:
                prog = rpm_getTagValueAsString( handle, RPMTAG_PREUNPROG );
                break;
            case rpmScriptPostuninstall:
                prog = rpm_getTagValueAsString( handle, RPMTAG_POSTUNPROG );
                break;
        }
    }
    char * data = NULL;
    switch( script )
    {
        case rpmScriptPreinstall:
            data = rpm_getTagValueAsString( handle, RPMTAG_PREIN );
            break;
        case rpmScriptPostinstall:
            data = rpm_getTagValueAsString( handle, RPMTAG_POSTIN );
            break;
        case rpmScriptPreuninstall:
            data = rpm_getTagValueAsString( handle, RPMTAG_PREUN );
            break;
        case rpmScriptPostuninstall:
            data = rpm_getTagValueAsString( handle, RPMTAG_POSTUN );
            break;

    }
    if( prog && data )
    {
        fprintf( out, "#!%s\n", prog );
        free( prog );
    }
    if( data )
    {
        fprintf( out, "%s", data );
        free( data );
    }
    fprintf( out, "\n" );

}

static void
PrintTagContents( RpmFileHandle handle, RpmTag theTag, FILE * out, FILE * errOut, int idx )
{
    if( theTag == RPMTAG_UNKNOWN )
    {
        fprintf( errOut, "error: incorrect format: unknown tag" );
        return;
    }

    RpmHeaderElementType t = GetHeaderTagType( handle, theTag, NULL );
    if( idx == -1 )
    {
        idx = 0;
    }
    switch( t )
    {
        case RPM_STRING_TYPE:
        {
            char * data = rpm_getTagValueAsString( handle, theTag );
            if( data )
            {
                fprintf( out, "%s", data );
            }
            free( data );
        }
        break;
        case RPM_STRING_ARRAY_TYPE:
        {

            char ** data;
            int numelem;
            GetHeaderTagAsStringArray( handle, theTag, &data, &numelem );
            if( data )
            {
                fprintf( out, "%s", data[idx] );
            }
            FreeStringArray( data, numelem );
        }
        break;
        case RPM_INT32_TYPE:
        {

            int32_t * data = GetHeaderTagAsPointer( handle, theTag, NULL, NULL );
            if( data )
            {
                fprintf( out, "%d", ntohl( data[idx] ) );
            }
        }
        break;
        case RPM_INT16_TYPE:
        {

            int16_t * data = GetHeaderTagAsPointer( handle, theTag, NULL, NULL );
            if( data )
            {
                fprintf( out, "%hd", ntohs( data[idx] ) );
            }
        }
        break;
        case RPM_INT64_TYPE:
        {

        }
        break;
        default:
            break;
    }
}

int
QueryFormatLoop( RpmFileHandle handle, const char * queryFormat, FILE * out, FILE * errOut, int iter )
{
    int maxelem = -1;

    const char * end_of_string = queryFormat+strlen( queryFormat );

    const char * ptr = queryFormat;

    while( ptr != end_of_string )
    {
        if( *ptr == '\\' )
        {
            if( ptr+1 == end_of_string )
            {
                fprintf( errOut, "Unfinished escape sequence" );
                return( 1 );
            }
            ++ptr;
            switch( *ptr )
            {
                case 'n': fprintf( out, "\n" ); break;
                case 'b': fprintf( out, "\b" ); break;
                case 't': fprintf( out, "\t" ); break;
                case '\\': fprintf( out, "\\" ); break;
                default: fprintf( out, "%c", *ptr ); break;
            }
        }
        else if( *ptr == '[' )
        {
            ++ptr;
            /* Read until we get to the full name. */
            const char * endptr = ptr;
            while( *endptr != ']' )
            {
                if( endptr == end_of_string )
                {
                    fprintf( errOut, "error: incorrect format: ] expected at end of array" );
                    return( 1 );
                }
                ++endptr;
            }
            char * param = strndup( ptr, endptr-ptr );
            int ret = QueryFormatLoop( handle, param, out, errOut, 0 );
            free( param );
            if( ret != 0 )
            {
                return( ret );
            }
            ptr = endptr;
        }
        else if( *ptr == '%' )
        {
            if( ptr+1 == end_of_string )
            {
                fprintf( errOut, "error: incorrect format: missing } after %%{" );
                return( 1 );
            }
            if( *( ptr+1 ) != '{' )
            {
                fprintf( errOut, "error: incorrect format: missing { after %%" );
                return( 1 );
            }
            else
            {
                ++ptr;
                /* Read until we get to the full name. */
                const char * endptr = ptr;
                while( *endptr != '}' )
                {
                    if( endptr == end_of_string )
                    {
                        fprintf( errOut, "error: incorrect format: missing } after %%{" );
                        return( 1 );
                    }
                    ++endptr;
                }
                char * param = strndup( ptr+1, endptr-ptr-1 );

                RpmTag theTag = GetHeaderTagFromString( param );
                if( theTag == RPMTAG_UNKNOWN )
                {
                    fprintf( errOut, "error: incorrect format: unknown tag %s", param );
                    return( 1 );
                }
                int thismaxelem = 0;

                GetHeaderTagType( handle, theTag, &thismaxelem );
                if( iter != -1 )
                {
                    if( maxelem != -1 &&  maxelem != thismaxelem )
                    {
                        fprintf( errOut, "error: incorrect format: array iterator used with different sized arrays" );
                        return( 1 );
                    }
                    maxelem =thismaxelem;
                }
                PrintTagContents( handle, theTag, out, errOut, iter );

                ptr = endptr;
            }
        }
        else
        {
            fprintf( out, "%c", *ptr );
        }
        ++ptr;
    }
    if( iter != -1 && iter < maxelem-1 )
    {
        QueryFormatLoop( handle, queryFormat, out, errOut, iter+1 );
    }

    return( 0 );

}

int
QueryFormat( RpmFileHandle handle, const char * queryFormat, FILE * out, FILE * errOut )
{
    return( QueryFormatLoop( handle, queryFormat, out, errOut, -1 ) );
}

int
RecursiveMakeDirectories( const char * directory )
{
    struct stat buf;
    const char * last_slash = strrchr( directory, '/' );
    char * parent = strndup( directory, last_slash-directory );

    int rc = stat( parent, &buf );

    if( rc != 0 )
    {
        /* Parent doesn't exist. */
        RecursiveMakeDirectories( parent );
    }

    mkdir( directory, 0777 );

    free( parent );
    return( 0 );

}

ssize_t
archive_callback ( struct archive * a, void *client_data, const void **buffer )
{
    (void)a;

    NetRequest *req = (NetRequest *)( client_data );

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    struct timeval timeout;
    int rc;

    int maxfd = -1;
    FD_ZERO( &fdread );
    FD_ZERO( &fdwrite );
    FD_ZERO( &fdexcep );

    /* set a suitable timeout to fail on */
    timeout.tv_sec = 5;         /* 1 minute */
    timeout.tv_usec = 0;

    /* get file descriptors from the transfers */
    curl_multi_fdset( req->multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd );

    /* In a real-world program you OF COURSE check the return code of the
     * function calls.  On success, the value of maxfd is guaranteed to be
     * greater or equal than -1.  We call select(maxfd + 1, ...), specially
     * in case of (maxfd == -1), we call select(0, ...), which is basically
     * equal to sleep. */

    rc = select( maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout );

    req->buffer_pos = 0;

    /* timeout or readable/writable sockets */
    /* note we *could* be more efficient and not wait for
     * CURLM_CALL_MULTI_PERFORM to clear here and check it on re-entry
     * but that gets messy */
    while( curl_multi_perform( req->multi_handle, &req->in_progress ) == CURLM_CALL_MULTI_PERFORM ) ;

    *buffer = req->buffer;

    return( req->buffer_pos );

}

void
ExtractArchiveContents( RpmFileHandle handle, const char * root, file_setup_callback * callback, void * userData )
{

    (void)callback;
    (void)userData;

    struct archive_entry *entry;
    int r;

    char fullPath[PATH_MAX];

    char curl_error_buffer[CURL_ERROR_SIZE];
    NetRequest readSession = {  handle->priv->curl,
                                curl_multi_init(),
                                NULL, 0, 0, 0,1 };
    char range_request[128];
    sprintf( range_request, "%zd-", handle->priv->payloadOffset );

    curl_easy_setopt( handle->priv->curl, CURLOPT_ERRORBUFFER, curl_error_buffer );
    curl_easy_setopt( handle->priv->curl, CURLOPT_RANGE, range_request );
    curl_easy_setopt( handle->priv->curl, CURLOPT_WRITEFUNCTION, netReadCallback );
    curl_easy_setopt( handle->priv->curl, CURLOPT_WRITEDATA, &readSession );
    curl_multi_add_handle( readSession.multi_handle, handle->priv->curl );

    while( curl_multi_perform( readSession.multi_handle, &readSession.in_progress ) == CURLM_CALL_MULTI_PERFORM ) ;

    struct archive *a = archive_read_new();
    archive_read_support_compression_all( a );
    archive_read_support_format_all( a );

    archive_read_open( a, &readSession, NULL, archive_callback, NULL );

    md5Param mp;

    void * buffer = malloc( READ_BUFFER_SIZE );

    while( archive_read_next_header( a, &entry ) == ARCHIVE_OK )
    {

        snprintf( fullPath, PATH_MAX, "%s/%s", root, archive_entry_pathname( entry ) );

        mode_t type = archive_entry_filetype( entry );
        if( S_ISDIR( type ) )
        {
            /* Only create the directory.*/
            RecursiveMakeDirectories( fullPath );
            /* TODO: Set the permissions. */
            archive_read_data_skip( a );
            //(*callback)(archive_entry_pathname(entry),fullPath,-1, userData);
        }
        else
        {

            md5Reset( &mp );
            /* Ensure the basename is there. */
            const char * last_slash = strrchr( fullPath, '/' );
            char * basename = strndup( fullPath, last_slash - fullPath );
            RecursiveMakeDirectories( basename );
            printf( "Creating %s\n", fullPath );
            int fd = open( fullPath, O_WRONLY|O_CREAT, 0666 );
            if( fd < 0 )
            {
                perror( "minirpm" );
                return;
            }

            ssize_t total_bytes_read = 0;
            ssize_t bytes_read;
            while( ( bytes_read = archive_read_data( a, buffer, READ_BUFFER_SIZE ) ) > 0 )
            {
                total_bytes_read+=bytes_read;
                ssize_t bytes_written = write( fd, buffer, bytes_read );
                if( bytes_written != bytes_read )
                {
                    break;
                }
                //md5Update(&mp,buffer,bytes_read);
            }

            printf( "Done.  %d written.\n", total_bytes_read );

            ( *callback )( archive_entry_pathname( entry ), fullPath, fd, userData );
            fdatasync( fd );
            close( fd );

            free( basename );
        }

    }
    r = archive_read_finish( a );

    curl_multi_remove_handle( readSession.multi_handle, handle->priv->curl );
    curl_multi_cleanup( readSession.multi_handle );
    free( readSession.buffer );

}

RpmHeaderElementType
GetHeaderTagType ( RpmFileHandle handle, RpmTag theTag, int * count )
{
    int i = 0;

    for( i = 0; i< handle->priv->numHeaderEntries; ++i )
    {
        if( handle->priv->headerEntries[i].tag == theTag )
        {
            if( count != NULL )
            {
                *count = handle->priv->headerEntries[i].count;
            }
            return( handle->priv->headerEntries[i].type );
        }

    }
    if( count != NULL )
    {
        *count = 0;
    }
    return( RPM_UNKNOWN_TYPE );
}
int
rpm_getNumTags( RpmFileHandle handle )
{
    return( handle->priv->numHeaderEntries );
}

char *
rpm_getTagValueAsString ( RpmFileHandle handle, RpmTag theTag )
{

    int i = 0;

    for( i = 0; i< handle->priv->numHeaderEntries; ++i )
    {
        if( handle->priv->headerEntries[i].tag == theTag )
        {
            return( strdup( handle->priv->headerStore + handle->priv->headerEntries[i].offset ) );
        }

    }
    return( 0 );
}

void *
GetHeaderTagAsPointer( RpmFileHandle handle, RpmTag theTag, RpmHeaderElementType * elementType, int * count )
{

    int i = 0;

    for( i = 0; i< handle->priv->numHeaderEntries; ++i )
    {
        if( handle->priv->headerEntries[i].tag == theTag )
        {
            if( count )
            {
                *count = handle->priv->headerEntries[i].count;
            }
            if( elementType )
            {
                *elementType = handle->priv->headerEntries[i].type;
            }
            return ( handle->priv->headerStore + handle->priv->headerEntries[i].offset );
        }
    }
    return( NULL );
}

void
GetHeaderTagAsStringArray( RpmFileHandle handle, RpmTag theTag, char *** ptrToStringArray, int * numElements )
{
    int i = 0;

    assert( ptrToStringArray != NULL );
    assert( numElements != NULL );

    *ptrToStringArray=NULL;

    for( i = 0; i< handle->priv->numHeaderEntries; ++i )
    {
        if( handle->priv->headerEntries[i].tag == theTag )
        {
            *numElements = handle->priv->headerEntries[i].count;
            int j = 0;
            char * offset = handle->priv->headerStore + handle->priv->headerEntries[i].offset;

            *ptrToStringArray = (char**)( calloc( *numElements, sizeof( char* ) ) );

            while( j < *numElements )
            {
                char * nextNull = memchr( offset, '\0', 1024 );
                ( *ptrToStringArray )[j] =  strndup( offset, nextNull-offset );
                offset = nextNull +1;
                j++;
            }

        }

    }
    return;
}

void
FreeStringArray( char ** stringArray, int numElements )
{
    if( stringArray )
    {
        for( int i = 0; i<numElements; ++i )
        {
            free( stringArray[i] );
        }
        free( stringArray );
    }
}

const char *
rpm_getTagAsString ( RpmTag theTag )
{
    for( size_t i = 0; i < sizeof( tagInfo )/sizeof( tagInfo[0] ); ++i )
    {
        if( tagInfo[i].value == theTag )
        {
            return( tagInfo[i].name );
        }
    }

    return( NULL );
}

RpmTag
GetHeaderTagFromString( const char * str )
{

    for( size_t i = 0; i < sizeof( tagInfo )/sizeof( tagInfo[0] ); ++i )
    {
        if( strcasecmp( tagInfo[i].name, str ) == 0 )
        {
            return( tagInfo[i].value );
        }
    }

    return( RPMTAG_UNKNOWN );
}
